Skip to content

loadConfig() falls back to ~/.gsd/defaults.json when no project config exists#1699

Closed
rodzved wants to merge 2 commits intogsd-build:mainfrom
rodzved:enhancement/loadconfig-defaults-fallback
Closed

loadConfig() falls back to ~/.gsd/defaults.json when no project config exists#1699
rodzved wants to merge 2 commits intogsd-build:mainfrom
rodzved:enhancement/loadconfig-defaults-fallback

Conversation

@rodzved
Copy link
Copy Markdown
Contributor

@rodzved rodzved commented Apr 4, 2026

Enhancement PR


Linked Issue

Closes #1683


What this enhancement improves

loadConfig() in core.cjs — the central config resolution used by all GSD commands.

Before / After

Before:
When .planning/config.json is missing, loadConfig() returns hardcoded defaults. ~/.gsd/defaults.json is never consulted. Pre-project commands like map-codebase ignore user globals entirely.

.planning/config.json → if missing → hardcoded defaults

After:
When .planning/config.json is missing, loadConfig() reads ~/.gsd/defaults.json as an intermediate fallback before falling back to hardcoded defaults.

hardcoded defaults ← ~/.gsd/defaults.json ← .planning/config.json

How it was implemented

  • get-shit-done/bin/lib/core.cjs — In the catch block of loadConfig(), added ~/.gsd/defaults.json reading with the same merge pattern used by buildNewProjectConfig() in config.cjs. Supports both flat keys (model_profile) and nested keys (git.branching_strategy, workflow.research). Includes depthgranularity migration.
  • docs/CONFIGURATION.md — Updated Global Defaults section to clarify defaults.json applies to all commands, not just /gsd-new-project. Added merge order documentation.
  • tests/core.test.cjs — Added 3 new tests: defaults.json fallback, no-defaults.json fallback, nested key support. Sandboxed HOME in loadConfig describe block to isolate from developer's real ~/.gsd/defaults.json.
  • tests/commands.test.cjs — Sandboxed HOME in resolve-model describe block for test isolation.
  • tests/config.test.cjs ��� Sandboxed HOME for config-ensure-section test.

Testing

How I verified the enhancement works

  • All 2159 existing tests pass
  • 3 new tests verify:
    1. defaults.json values override hardcoded defaults when no config.json exists
    2. Hardcoded defaults are returned when neither config.json nor defaults.json exists
    3. Nested keys (git.*, workflow.*) are correctly resolved from defaults.json

Platforms tested

  • macOS
  • Windows (including backslash path handling)
  • Linux
  • N/A (not platform-specific)

Runtimes tested

  • Claude Code
  • Gemini CLI
  • OpenCode
  • Other: ___
  • N/A (not runtime-specific)

Scope confirmation

  • The implementation matches the scope approved in the linked issue — no additions or removals
  • If scope changed during implementation, I updated the issue and got re-approval before continuing

Checklist

  • Issue linked above with Closes #NNNPR will be auto-closed if missing
  • Linked issue has the approved-enhancement label — PR will be closed if missing
  • Changes are scoped to the approved enhancement — nothing extra included
  • All existing tests pass (npm test)
  • New or updated tests cover the enhanced behavior
  • CHANGELOG.md updated
  • Documentation updated if behavior or output changed
  • No unnecessary dependencies added

Breaking changes

None. The merge order is additive — existing .planning/config.json files take full precedence as before. Only the "no config.json exists" path changes, and that path currently returns hardcoded defaults which have no user-facing contract.

@rodzved rodzved requested a review from glittercowboy as a code owner April 4, 2026 19:51
@rodzved
Copy link
Copy Markdown
Contributor Author

rodzved commented Apr 4, 2026

Update: Refactored the catch fallback in loadConfig() to use a flat merge loop instead of duplicating every key individually.

Before: Each config key was listed explicitly in the return object (29 lines), meaning any new key added to loadConfig() would need to be duplicated in the fallback path — a maintenance trap.

After: The fallback iterates over userDefaults entries, flattens nested sections (git.*, workflow.*) to top-level keys, and merges them into the defaults object. New keys added to defaults are automatically picked up without changes to the fallback path.

All 2159 tests still pass.

@trek-e
Copy link
Copy Markdown
Collaborator

trek-e commented Apr 4, 2026

This PR needs a rebase onto main. PR #1708 just merged, which refactored loadConfig() defaults into CONFIG_DEFAULTS — the same function this PR modifies. After rebase, the catch block should reference CONFIG_DEFAULTS instead of the local defaults object (which no longer exists).

CI checks are also missing — likely due to the stale base.

@trek-e trek-e added the needs merge fixes CI failing or merge conflicts need resolution label Apr 4, 2026
rodzved added 2 commits April 5, 2026 04:59
Avoids maintenance trap where new config keys added to the try block
would need to be duplicated in the catch fallback path.
@rodzved rodzved force-pushed the enhancement/loadconfig-defaults-fallback branch from 994d675 to f7878f4 Compare April 4, 2026 21:00
@rodzved
Copy link
Copy Markdown
Contributor Author

rodzved commented Apr 4, 2026

Rebased onto main to pick up #1708 (CONFIG_DEFAULTS refactor). The catch block's { ...defaults } now spreads CONFIG_DEFAULTS via const defaults = CONFIG_DEFAULTS. All 2164 tests pass.

Copy link
Copy Markdown
Collaborator

@trek-e trek-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: loadConfig() defaults.json fallback

Verdict: Request Changes

Merge conflict status

The needs merge fixes label appears stale -- GitHub reports this PR as MERGEABLE. PR #1708 (CONFIG_DEFAULTS refactor, commit 7712595) has since merged and extracted the inline defaults into CONFIG_DEFAULTS. Because the catch-block change is in a separate hunk from the constant extraction, git can auto-merge. However, the author should rebase onto main and confirm the catch block references CONFIG_DEFAULTS (via const defaults = CONFIG_DEFAULTS) rather than the old inline object, then re-run tests to verify.

Issues found

1. Side-effect write in a read function (design concern)

loadConfig() is a read-path function, but the depth-to-granularity migration writes back to ~/.gsd/defaults.json (line ~377 in the diff). This creates a race condition if multiple GSD commands call loadConfig() concurrently (e.g., parallel subagents). The migration write-back should be removed from loadConfig() and handled by a dedicated migration path, or at minimum documented as intentional. The same migration already exists in buildNewProjectConfig() in config.cjs -- loadConfig() should read-only and let the existing migration handle persistence.

Suggested fix: Remove the fs.writeFileSync call in the catch block. Keep the in-memory depth -> granularity remap so the returned config is correct, but do not persist the change from within loadConfig().

2. Missing test for depth-to-granularity migration in the fallback path

The depth migration logic in the catch block (lines ~373-379) has no test coverage. Add a test that places { "depth": "comprehensive" } in defaults.json and asserts config.granularity === 'fine'.

3. CHANGELOG.md not updated

The PR checklist shows CHANGELOG.md is unchecked. This is a user-facing behavior change (pre-project commands now read global defaults) and should have an entry.

What looks good

  • Correct use of node:test / node:assert/strict / CommonJS -- style compliant
  • No external dependencies added
  • HOME sandboxing in all affected test files prevents leakage from developer environments
  • The if (key in flat) guard prevents arbitrary key injection from defaults.json
  • Nested key flattening correctly mirrors the try-block's get() helper pattern
  • Closes #1683 present, issue has approved-enhancement label
  • Documentation update in CONFIGURATION.md is clear and accurate

Minor

  • The workflow.plan_check -> plan_checker mapping is correct but only covers one alias. If more aliases are added to the try-block in the future, this mapping will silently drift. Consider extracting the alias map to a shared constant. (Non-blocking, just a note.)

@trek-e trek-e added the review: changes requested PR reviewed — changes required before merge label Apr 5, 2026
@rodzved
Copy link
Copy Markdown
Contributor Author

rodzved commented Apr 5, 2026

Closing — this was implemented by the maintainer in #1738. Thanks for the review feedback, it was incorporated into the final version.

@rodzved rodzved closed this Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs merge fixes CI failing or merge conflicts need resolution review: changes requested PR reviewed — changes required before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Apply ~/.gsd/defaults.json as fallback for pre-project commands

2 participants